package org.jslaughter.component.physics.motion;

import org.jbox2d.collision.Segment;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.World;
import org.jslaughter.component.ai.BehaviourImpl;
import org.jslaughter.event.eventtype.DamageEvent;
import org.jslaughter.event.eventtype.DamageEvent.DAMAGE_TYPE;
import org.newdawn.slick.Image;
import org.newdawn.slick.geom.Vector2f;
import org.nvframe.component.ai.Behaviour.BEHAVIOUR_TYPE;
import org.nvframe.component.state.Position;
import org.nvframe.entity.Entity;
import org.nvframe.event.EventService;
import org.nvframe.event.eventtype.RenderEvent;
import org.nvframe.event.eventtype.RenderListener;
import org.nvframe.event.eventtype.UpdateEvent;
import org.nvframe.event.eventtype.UpdateListener;
import org.nvframe.exception.NVFrameException;
import org.nvframe.exception.NVFrameRuntimeException;
import org.nvframe.manager.RegisterManager;
import org.nvframe.manager.ResourceManager;
import org.nvframe.util.NVMathUtils;
import org.nvframe.util.settings.SettingsObj;

/**
 * 
 * @author Nik Van Looy
 */
public class FastBulletMotion extends BulletMotion implements UpdateListener, RenderListener {

	private float moveSpeed;
	private float offset;
	private float minDamagePoints;
	private float maxDamagePoints;

	private Vector2f velocity = new Vector2f();
	private Vector2f startCoords = new Vector2f();
	private boolean blocked = false;
	private Entity target = null;
	private int currentHitTimeMs = 0;
	private int hitTimeMs = 0;
	private World world;
	
	private float trailDistance = 0;
	private Image bulletTrail = null;

	public FastBulletMotion(String id, Entity owner, SettingsObj settings) throws NVFrameException {
		super(id, owner);
		
		minDamagePoints = settings.getInt("minDamagePoints", 0);
		maxDamagePoints = settings.getInt("maxDamagePoints", 0);
		moveSpeed = settings.getFloat("velocity");	
		offset = settings.getFloat("offset", 0);
		
		if(settings.containsKey("bulletTrail")) {
			String trailImage = settings.getString("bulletTrail");
			
			try {
				bulletTrail = ResourceManager.getInstance().getImage(trailImage);
			} catch (NVFrameException e) {
				throw new NVFrameException("cannot load bullettrail - entity: " + owner.getId() + ", bulletTrail: " + trailImage);
			}
		}
		
		world = (World) RegisterManager.getInstance().getOption("world");
	}
	
	private int calculateDamagePoints() {
		return (int) NVMathUtils.getRandomNumberBetween(minDamagePoints, maxDamagePoints);
	}
	
	/**
	 * 
	 * @param startCoords
	 * @param targetCoords
	 * @param offset
	 */
	public void fire(Vector2f startCoords, Vector2f targetCoords, float offset) {
		this.offset = offset;
		fire(startCoords.copy(), targetCoords.copy());
	}

	/**
	 * 
	 * @param startCoords
	 * @param targetCoords
	 */
	public void fire(Vector2f startCoords, Vector2f targetCoords) {
		this.startCoords.set(startCoords);

		Position positionComp = (Position) owner.getComponent(Position.class);
		
		if(positionComp == null)
			throw new NVFrameRuntimeException("cant start motion without Position Component");

		// calculate the shot-direction and apply an offset (so bullet doesn't hit shooter)
		float radDirection = NVMathUtils.calculateDirectionRad(startCoords, targetCoords);
		positionComp.setDirection((float) Math.toDegrees(radDirection));
		positionComp.setXY(startCoords.getX() + (float) Math.cos(radDirection) * offset, 
				startCoords.getY() + (float) Math.sin(radDirection) * offset);
		
		// translate velocity to a vector
		velocity.x = (float) Math.cos(radDirection) * moveSpeed;
		velocity.y = (float) Math.sin(radDirection) * moveSpeed;
		
		// get the behaviour of the owner of the weapon
		BehaviourImpl behaviour = (BehaviourImpl) owner.getRoot().getComponent(BehaviourImpl.class);
		
		// calculate coord for raycast destination
		Vector2f rayVec = new Vector2f();
		rayVec.x = (float) (startCoords.x + (1000 * Math.cos(radDirection)));
		rayVec.y = (float) (startCoords.y + (1000 * Math.sin(radDirection)));
		
		// use raycast to see if bullet hits something
		Shape[] shapes = new Shape[5];
		Segment segment = new Segment();
		segment.p1.set(startCoords.x / NVMathUtils.PIXELS_PER_METER, startCoords.y / NVMathUtils.PIXELS_PER_METER);
		segment.p2.set(rayVec.x / NVMathUtils.PIXELS_PER_METER, rayVec.y / NVMathUtils.PIXELS_PER_METER);
		
		world.raycast(segment, shapes, shapes.length, false, null);
		
		for(Shape shape : shapes) {
			if(shape == null)
				continue;
			
			// bullet hits a target!
			Body hitBody = shape.getBody();
			Entity rayEntity = (Entity) hitBody.getUserData();

			Position rayPosition = (Position) rayEntity.getComponent(Position.class);

			float distance = startCoords.distance(rayPosition.getXY());
			
			// calculate time it takes till it hits the body
			hitTimeMs = (int) (distance / moveSpeed);

			// collided with a entity that blocks bullets (eg a wall)
			if(behaviour.damageGetsBlockedBy(rayEntity, DAMAGE_TYPE.WEAPON_BULLET)) {
				blocked = true;
				break;
			}
			
			// check behaviour towards receiver
			if(behaviour.canDoDamageTo(rayEntity, DAMAGE_TYPE.WEAPON_BULLET) &&
					behaviour.getBehaviourTowards(rayEntity) != BEHAVIOUR_TYPE.FRIENDLY) {
				target = rayEntity;
				break;
			}
		}
		
		// init bulletTrail
		if(bulletTrail != null) {
			bulletTrail.setCenterOfRotation(0, bulletTrail.getHeight());
			bulletTrail.setRotation((float) Math.toDegrees(radDirection)-180);
		}
		
		// start listening for update events
		EventService.getInstance().addEventListener(this);
	}

	@Override
	public void onUpdate(UpdateEvent event) {
		int delta = event.getDelta();
		Position positionComp = (Position) owner.getComponent(Position.class);
		
		positionComp.setX(positionComp.getX() + (velocity.x * delta));
		positionComp.setY(positionComp.getY() + (velocity.y * delta));
		
		// bulletTrail
		if(bulletTrail != null) {
			trailDistance = (float) Math.sqrt(Math.pow(positionComp.getX() - startCoords.x, 2) + 
					Math.pow(positionComp.getY() - startCoords.y, 2));
			trailDistance = trailDistance - 40 > 300 ? 300 : trailDistance - 40;
		}
		
		currentHitTimeMs += delta;
		
		if(hitTimeMs != 0 && currentHitTimeMs <= hitTimeMs)
			return;
		
		// send a DamageEvent to the target
		if(target != null) {
			float hitDirection = (float) Math.toDegrees(NVMathUtils.calculateDirectionRad(startCoords, positionComp.getXY()));
			
			EventService.getInstance().fireEvent(new DamageEvent(owner, 
					target, 
					calculateDamagePoints(),
					DAMAGE_TYPE.WEAPON_BULLET,
					hitDirection));
			
			EventService.getInstance().removeEventListener(this);
			owner.removed();
		}
		else if(blocked) {
			EventService.getInstance().removeEventListener(this);
			owner.removed();
		}
	}

	@Override
	public void onRender(RenderEvent event) {
		Position positionComp = (Position) owner.getComponent(Position.class);
		bulletTrail.draw(positionComp.getX(), positionComp.getY(), trailDistance, bulletTrail.getHeight());
	}
}
